package com.daxue.common.http.core;

import com.daxue.common.http.exception.RestConnectException;
import com.daxue.common.http.exception.RestContentException;
import com.daxue.common.http.utils.AlertUtils;
import com.daxue.common.http.utils.CollectionsUtils;
import com.daxue.common.http.utils.RateLimiterConfig;
import com.daxue.common.http.utils.SelfProxyAware;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.lang.NonNull;
import org.springframework.retry.annotation.Retryable;
import org.springframework.util.StringUtils;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestClientException;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;
import java.time.Duration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author daxue0929
 * @date 2022/4/7
 */

public interface RestClient extends RestTemplateGetter, SelfProxyAware<RestClient>, ReConfigurable<RestClient.RestClientResourceConfig>{

    public String getClientName();

    ParameterizedTypeReference<JsonNode> JSON_RESPONSE_TYPE = new ParameterizedTypeReference<JsonNode>() {
    };
    ParameterizedTypeReference<Map<String, Object>> MAP_RESPONSE_TYPE = new ParameterizedTypeReference<Map<String, Object>>() {
    };
    ParameterizedTypeReference<List<Map<String, Object>>> LIST_MAP_RESPONSE_TYPE = new ParameterizedTypeReference<List<Map<String, Object>>>() {
    };

    static HttpHeaders jsonHeader() {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        return headers;
    }

    static <T> HttpEntity<T> customEntity(T body, @NonNull HttpHeaders headers) {
        if (headers == null) {
            throw new NullPointerException("headers");
        } else {
            return new HttpEntity(body, headers);
        }
    }

    static URI genRequestUri(@NonNull final String baseUrl) {
        if (baseUrl == null) {
            throw new NullPointerException("baseUrl");
        } else {
            return genRequestUri(baseUrl, CollectionsUtils.mapOf());
        }
    }

    static URI genRequestUri(@NonNull final String baseUrl, @NonNull final Map<String, Object> queryParam) {
        if (baseUrl == null) {
            throw new NullPointerException("baseUrl");
        } else if (queryParam == null) {
            throw new NullPointerException("queryParam");
        } else {
            return genRequestUriBuilder(baseUrl, queryParam).build().toUri();
        }
    }

    static URI genRequestUri(@NonNull final String baseUrl, @NonNull final Object... uriVariables) {
        if (baseUrl == null) {
            throw new NullPointerException("baseUrl");
        } else if (uriVariables == null) {
            throw new NullPointerException("uriVariables");
        } else {
            return genRequestUri(baseUrl, CollectionsUtils.mapOf(), uriVariables);
        }
    }

    static URI genRequestUri(@NonNull final String baseUrl, @NonNull final Map<String, Object> queryParam, @NonNull final Object... uriVariables) {
        if (baseUrl == null) {
            throw new NullPointerException("baseUrl");
        } else if (queryParam == null) {
            throw new NullPointerException("queryParam");
        } else if (uriVariables == null) {
            throw new NullPointerException("uriVariables");
        } else {
            return genRequestUriBuilder(baseUrl, queryParam).build(uriVariables);
        }
    }

    static UriComponentsBuilder genRequestUriBuilder(@NonNull final String baseUrl, @NonNull final Map<String, Object> queryParam) {
        if (baseUrl == null) {
            throw new NullPointerException("baseUrl");
        } else if (queryParam == null) {
            throw new NullPointerException("queryParam");
        } else {
            UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(baseUrl);
            Iterator var3 = queryParam.entrySet().iterator();

            while(var3.hasNext()) {
                Map.Entry<String, Object> entry = (Map.Entry)var3.next();
                builder.queryParam((String)entry.getKey(), new Object[]{entry.getValue()});
            }

            return builder;
        }
    }

    @Retryable({RestConnectException.class})
    default <T, R> ResponseEntity<R> doExchange(URI uri, HttpMethod method, HttpHeaders headers, T body, ParameterizedTypeReference<R> typeReference) throws RestConnectException,
        RestContentException {
        try {
            return this.getRestTemplate().exchange(uri, method, customEntity(body, headers), typeReference);
        } catch (ResourceAccessException var7) {
            throw new RestConnectException(var7);
        } catch (HttpClientErrorException var8) {
            if (StringUtils.hasText(var8.getResponseBodyAsString())) {
                throw new RestContentException(var8.getResponseBodyAsString());
            } else {
                throw new RestContentException(var8.getLocalizedMessage() + ": unknown reason");
            }
        } catch (RestClientException var9) {
            throw new RestContentException(var9);
        }
    }


    <T, R> ResponseEntity<R> exchange(URI uri, HttpMethod method, HttpHeaders headers, T body, ParameterizedTypeReference<R> typeReference) throws RestConnectException, RestContentException;

    <T> ResponseEntity<T> get(URI uri, HttpHeaders headers, ParameterizedTypeReference<T> typeReference) throws RestConnectException, RestContentException;

    <T> ResponseEntity<T> get(URI uri, ParameterizedTypeReference<T> typeReference) throws RestConnectException, RestContentException;

    ResponseEntity<Map<String, Object>> get(URI uri, HttpHeaders headers) throws RestConnectException, RestContentException;

    ResponseEntity<Map<String, Object>> get(URI uri) throws RestConnectException, RestContentException;

    <T, R> ResponseEntity<R> post(URI uri, HttpHeaders headers, T body, ParameterizedTypeReference<R> typeReference) throws RestConnectException, RestContentException;

    <T, R> ResponseEntity<R> post(URI uri, T body, ParameterizedTypeReference<R> typeReference) throws RestConnectException, RestContentException;

    <T> ResponseEntity<Map<String, Object>> post(URI uri, HttpHeaders headers, T jsonBody) throws RestConnectException, RestContentException;

    ResponseEntity<Map<String, Object>> post(URI uri, HttpHeaders headers) throws RestConnectException, RestContentException;

    <T> ResponseEntity<Map<String, Object>> post(URI uri, T jsonBody) throws RestConnectException, RestContentException;

    <T, R> ResponseEntity<R> put(URI uri, HttpHeaders headers, T jsonBody, ParameterizedTypeReference<R> typeReference) throws RestConnectException, RestContentException;

    <T, R> ResponseEntity<R> put(URI uri, T jsonBody, ParameterizedTypeReference<R> typeReference) throws RestConnectException, RestContentException;

    <T> ResponseEntity<Map<String, Object>> put(URI uri, HttpHeaders headers, T jsonBody) throws RestConnectException, RestContentException;

    ResponseEntity<Map<String, Object>> put(URI uri, HttpHeaders headers) throws RestConnectException, RestContentException;

    <T> ResponseEntity<Map<String, Object>> put(URI uri, T jsonBody) throws RestConnectException, RestContentException;

    <T> ResponseEntity<T> delete(URI uri, HttpHeaders headers, ParameterizedTypeReference<T> typeReference) throws RestConnectException, RestContentException;

    <T> ResponseEntity<T> delete(URI uri, ParameterizedTypeReference<T> typeReference) throws RestConnectException, RestContentException;

    ResponseEntity<Map<String, Object>> delete(URI uri, HttpHeaders headers) throws RestConnectException, RestContentException;

    ResponseEntity<Map<String, Object>> delete(final URI uri) throws RestConnectException, RestContentException;

    default <T> T responseBaseCheck(final ResponseEntity<T> entity) throws RestContentException {
        AlertUtils.notNull(entity, () -> {
            return new RestContentException(AlertUtils.alertMessage("[%s] Empty response.", new Object[]{this.getClientName()}));
        });
        AlertUtils.isTrue(entity.getStatusCode().is2xxSuccessful(), () -> {
            return new RestContentException(AlertUtils.alertMessage("[%s] StatusCode: %d, ReasonPhrase %s.", new Object[]{this.getClientName(), entity.getStatusCodeValue(), entity.getStatusCode().getReasonPhrase()}));
        });
        return AlertUtils.notNull(entity.getBody(), () -> {
            return new RestContentException(AlertUtils.alertMessage("[%s] Empty response entity.", new Object[]{this.getClientName()}));
        });
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class RestClientResourceConfig {
        public static final Duration DEFAULT_CONNECT_TIMEOUT = Duration.ofSeconds(5L);
        public static final Duration DEFAULT_READ_TIMEOUT = Duration.ofSeconds(10L);
        private long connectTimeoutMillis;
        private long readTimeoutMillis;
        private String proxy;
        private Set<String> logging;
        private List<RateLimiterConfig> rateLimits;
        private Map<String, Object> customConfig;

        public Duration connectDuration() {
            return this.connectTimeoutMillis <= 0L ? DEFAULT_CONNECT_TIMEOUT : Duration.ofMillis(this.connectTimeoutMillis);
        }

        public Duration readDuration() {
            return this.readTimeoutMillis <= 0L ? DEFAULT_READ_TIMEOUT : Duration.ofMillis(this.readTimeoutMillis);
        }

        public RestClientResourceConfig() {
        }

        public long getConnectTimeoutMillis() {
            return this.connectTimeoutMillis;
        }

        public long getReadTimeoutMillis() {
            return this.readTimeoutMillis;
        }

        public String getProxy() {
            return this.proxy;
        }

        public Set<String> getLogging() {
            return this.logging;
        }

        public List<RateLimiterConfig> getRateLimits() {
            return this.rateLimits;
        }

        public Map<String, Object> getCustomConfig() {
            return this.customConfig;
        }

        public RestClient.RestClientResourceConfig setConnectTimeoutMillis(final long connectTimeoutMillis) {
            this.connectTimeoutMillis = connectTimeoutMillis;
            return this;
        }

        public RestClient.RestClientResourceConfig setReadTimeoutMillis(final long readTimeoutMillis) {
            this.readTimeoutMillis = readTimeoutMillis;
            return this;
        }

        public RestClient.RestClientResourceConfig setProxy(final String proxy) {
            this.proxy = proxy;
            return this;
        }

        public RestClient.RestClientResourceConfig setLogging(final Set<String> logging) {
            this.logging = logging;
            return this;
        }

        public RestClient.RestClientResourceConfig setRateLimits(final List<RateLimiterConfig> rateLimits) {
            this.rateLimits = rateLimits;
            return this;
        }

        public RestClient.RestClientResourceConfig setCustomConfig(final Map<String, Object> customConfig) {
            this.customConfig = customConfig;
            return this;
        }

        public boolean equals(final Object o) {
            if (o == this) {
                return true;
            } else if (!(o instanceof RestClient.RestClientResourceConfig)) {
                return false;
            } else {
                RestClient.RestClientResourceConfig other = (RestClient.RestClientResourceConfig)o;
                if (!other.canEqual(this)) {
                    return false;
                } else if (this.getConnectTimeoutMillis() != other.getConnectTimeoutMillis()) {
                    return false;
                } else if (this.getReadTimeoutMillis() != other.getReadTimeoutMillis()) {
                    return false;
                } else {
                    label64: {
                        Object this$proxy = this.getProxy();
                        Object other$proxy = other.getProxy();
                        if (this$proxy == null) {
                            if (other$proxy == null) {
                                break label64;
                            }
                        } else if (this$proxy.equals(other$proxy)) {
                            break label64;
                        }

                        return false;
                    }

                    label57: {
                        Object this$logging = this.getLogging();
                        Object other$logging = other.getLogging();
                        if (this$logging == null) {
                            if (other$logging == null) {
                                break label57;
                            }
                        } else if (this$logging.equals(other$logging)) {
                            break label57;
                        }

                        return false;
                    }

                    Object this$rateLimits = this.getRateLimits();
                    Object other$rateLimits = other.getRateLimits();
                    if (this$rateLimits == null) {
                        if (other$rateLimits != null) {
                            return false;
                        }
                    } else if (!this$rateLimits.equals(other$rateLimits)) {
                        return false;
                    }

                    Object this$customConfig = this.getCustomConfig();
                    Object other$customConfig = other.getCustomConfig();
                    if (this$customConfig == null) {
                        if (other$customConfig != null) {
                            return false;
                        }
                    } else if (!this$customConfig.equals(other$customConfig)) {
                        return false;
                    }

                    return true;
                }
            }
        }

        protected boolean canEqual(final Object other) {
            return other instanceof RestClient.RestClientResourceConfig;
        }

        public int hashCode() {
            boolean PRIME = true;
            int result = 1;
            long $connectTimeoutMillis = this.getConnectTimeoutMillis();
            result = result * 59 + (int)($connectTimeoutMillis >>> 32 ^ $connectTimeoutMillis);
            long $readTimeoutMillis = this.getReadTimeoutMillis();
            result = result * 59 + (int)($readTimeoutMillis >>> 32 ^ $readTimeoutMillis);
            Object $proxy = this.getProxy();
            result = result * 59 + ($proxy == null ? 43 : $proxy.hashCode());
            Object $logging = this.getLogging();
            result = result * 59 + ($logging == null ? 43 : $logging.hashCode());
            Object $rateLimits = this.getRateLimits();
            result = result * 59 + ($rateLimits == null ? 43 : $rateLimits.hashCode());
            Object $customConfig = this.getCustomConfig();
            result = result * 59 + ($customConfig == null ? 43 : $customConfig.hashCode());
            return result;
        }

        public String toString() {
            return "RestClient.RestClientResourceConfig(connectTimeoutMillis=" + this.getConnectTimeoutMillis() + ", readTimeoutMillis=" + this.getReadTimeoutMillis() + ", proxy=" + this.getProxy() + ", logging=" + this.getLogging() + ", rateLimits=" + this.getRateLimits() + ", customConfig=" + this.getCustomConfig() + ")";
        }
    }

}
